package calculators;

/**
 * Performs fourier and inverse fourier transforms
 * @author Jeff Hunter, Peter Schilling
 */
public class FourierCalculator {
	public static final double TWO_PI = Math.PI * 2;
	/**
	 * Fourier transformation
	 * @param samples
	 * @return fourier'd samples
	 */
	public static ComplexNum[] forward(double[] samples, int binCount) {
		int N = binCount;
		int sampleCount = samples.length;
		ComplexNum[] result = new ComplexNum[N];

		for (int f = 0; f < N; f++) {
			result[f] = new ComplexNum(0, 0);
			for (int t = 0; t < sampleCount; t++) {
				double twoPITFoN = (TWO_PI * t * f) / N;
				result[f].setReal(result[f].getReal() + (samples[t] * Math.cos(twoPITFoN)));
				result[f].setImag(result[f].getImag() + (samples[t] * -Math.sin(twoPITFoN)));
			}
		}
		return result;
	}

	public static double[] inverse(ComplexNum[] samples) {
		int N = samples.length;
		double[] invSamples = new double[samples.length];

		for (int t = 0; t < N; t++) {
			invSamples[t] = 0;
			for (int f = 0; f < N; f++) {
				double twoPITFoN = (TWO_PI * t * f) / N;
				invSamples[t] -= (samples[f].getReal() * Math.cos(twoPITFoN)) + (samples[f].getImag() * Math.sin(twoPITFoN));
			}
			invSamples[t] /= N;
		}
		return invSamples;
	}

	public static double[] inverse(double[] samples) {
		int N = samples.length;
		double[] invSamples = new double[samples.length];

		for (int t = 0; t < N; t++) {
			invSamples[t] = 0;
			for (int f = 0; f < N; f++)
				invSamples[t] -= (samples[f] * Math.cos((TWO_PI * t * f) / N));
			invSamples[t] /= N;
		}
		return invSamples;
	}

	/**
	 * Applies fourier and returns amplitude
	 * @param samples
	 * @return fourier'd amplitude
	 */
	public static double[] amplitude(double[] samples, int binCount) {
		ComplexNum[] intermediate = forward(samples, binCount);
		double[] results = new double[intermediate.length];

		for (int i = 0; i < intermediate.length; i++) {
			results[i] = Math.sqrt(Math.pow(intermediate[i].getReal(), 2) + Math.pow(intermediate[i].getImag(), 2));
			results[i] = results[i] / (binCount / 2);
		}
		return results;
	}

	public static double[] amplitude(short[] samples, int binCount) {
		double[] dsamples = new double[samples.length];
		for (int i = 0; i < samples.length; i++)
			dsamples[i] = samples[i];
		return amplitude(dsamples, binCount);
	}

	public static double[] amplitude(byte[] samples, int binCount) {
		double[] dsamples = new double[samples.length];
		for (int i = 0; i < dsamples.length; i++)
			dsamples[i] = (((int) samples[i] & 0x000000FF) - 128);
		return amplitude(dsamples, binCount);
	}

	/**
	 * performs a phase shift on samples
	 * @param samples
	 * @return phase shift
	 */
	public static double[] phaseShift(double[] samples, int binCount) {
		ComplexNum[] intermediate = forward(samples, binCount);
		double[] results = new double[samples.length];

		for (int i = 0; i < intermediate.length; i++)
			results[i] = Math.atan(intermediate[i].getReal() / intermediate[i].getImag());
		return results;
	}

	/**
	 * for testing forward and inverse dft
	 * @param argv
	 */
	public static void main(String[] argv) {
		double[] test = new double[100];

		for (int i = 0; i < test.length; i++)
			test[i] = Math.sin(TWO_PI * i / test.length) * 10;

		ComplexNum c[] = forward(test, test.length);
		double amp[] = amplitude(test, test.length);

		for (int i = 0; i < c.length; i++)
			System.out.println("B[" + i + "]: " + c[i] + ", T[" + i + "]=" + test[i] + ", A[" + i + "]: " + amp[i]);

		double[] inv = inverse(c);
		for (int i = 0; i < inv.length; i++)
			System.out.println("inv[" + i + "]: " + inv[i] + ", t[" + i + "]=" + test[i] + ", D[" + i + "]: " + Math.abs(test[i] - inv[i]));
		System.out.println();
	}

	/**
	 * holds a complex number
	 */
	static public class ComplexNum {

		private double real_;
		private double imag_;

		ComplexNum() {
			this.real_ = 0;
			this.imag_ = 0;
		}

		ComplexNum(double a, double b) {
			this.real_ = a;
			this.imag_ = b;
		}

		public double getImag() {
			return imag_;
		}

		public double getReal() {
			return real_;
		}

		public void setImag(double imag) {
			this.imag_ = imag;
		}

		public void setReal(double real) {
			this.real_ = real;
		}

		public String toString() {
			return real_ + " | " + imag_;
		}
	}
}


